SpringMVC 控制器方法(handler)的映射 - HandlerMapping

SpringMVC 控制器方法(handler)的映射 - HandlerMapping

简单源码分析

HandlerMapping 接口的作用是定义请求(request)和处理程序对象(handler)之间映射。

这个类可以由应用程序开发人员实现,当然这是没有必要的,因为框架中包含了 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingorg.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,并且会自动注入 IOC 容器,自动在在 DispatcherServletdoDispatch 方法中生效。

HandlerMapping 的实现可以支持拦截器,但不是必须这样做。处理程序总是被包装在 HandlerExecutionChain 实例中,可选地附带一些 HandlerInterceptor 实例。DispatcherServlet 将首先以给定的顺序调用每个 HandlerInterceptorpreHandle 方法,如果所有 preHandle 方法都返回 true,则最后调用处理程序本身。

具体的调用顺序请看《SpringMVC- 第二篇:控制器方法(handler)映射.md》中的 HandlerExecutionChain 和 DispatcherServlet中的源码分析 小节

参数化映射的能力是这个 MVC 框架的一个强大而不寻常的功能。例如,可以根据会话状态、cookie 状态或许多其他变量编写自定义映射。似乎没有其他 MVC 框架具有同样的灵活性。

HandlerMapping 有很多实现类,我们主要看 AbstractHandlerMapping 的子类。

接口源码分析:

HandlerMapping 的接口源码很有意思,除了只包含一个待实现的方法 getHandler 之外,还包含很多请求域属性名,在通过请求获取了 handler 之后,会将这个映射过程中的一些中间变量存放到这些请求域属性中,方便我们在控制器中来获取这些中间变量。

public interface HandlerMapping {

    // 在处理器映射过程中最匹配的 handler  ,也就是最终处理请求的 handler
    String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";

    // 请求路径中 用于匹配 handler的 部分路径 已弃用
    String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";

    // 处理器映射过程中使用的请求路径
    // 注意,这个参数并不被所有的类型的HandlerMapping支持
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

    // 处理器映射过程中最匹配 请求路径的 控制器的路径模板
    // 注意,这个参数并不被所有的类型的HandlerMapping支持
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

    // (控制器)类级别的映射信息是否会被参考
    // 注意,这个参数并不被所有的类型的HandlerMapping支持
    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

    // 获取请求路径参数
    // 注意,这个参数并不被所有的类型的HandlerMapping支持
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

    // 获取请求路径的矩阵参数
    // 注意,这个参数并不被所有的类型的HandlerMapping支持
    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

    // 获取此处理器映射映射的handler可以生产的 MediaType 的集合
    // 注意,这个参数并不被所有的类型的HandlerMapping支持
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    // 
    default boolean usesPathPatterns() {
        return false;
    }

    // 根据请求中的信息,比如路径,session的状态,等返回包装了handler的 HandlerExecutionChain
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

简单实践:

@RestController
@RequestMapping("/handlerMappingAttribute")
public class HandlerMappingAttributeTestController {

    @RequestMapping("/allAtrtribute/**")
    public String getAllAttribute(HttpServletRequest request){
        Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            System.out.println(handlerMethod.toString());
        }
        Object attribute = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        System.out.println("用于映射的请求路径:"+attribute);
        Object attribute1 = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        System.out.println("最匹配的handler的路径模板:"+attribute1);
        Object attribute2 = request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
        Object attribute3 = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        Object attribute4 = request.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
        Object attribute5 = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        return "";
    }

}

访问链接 http://localhost:8080/SpringMVC_AnnotationConfig/handlerMappingAttribute/allAtrtribute

日志:

xyz.xiashuo.springmvcannotationconfig.controller.HandlerMappingAttributeTestController#getAllAttribute(HttpServletRequest)
用于映射的请求路径:/handlerMappingAttribute/allAtrtribute
最匹配的handler的路径模板:/handlerMappingAttribute/allAtrtribute/**

实际上,这几个属性里面,最好用的就是前三个

与 HandlerInterceptor 的联系

具体请看《SpringMVC- 第二篇:控制器方法(handler)映射.md》中的 控制器方法(handler)拦截器 小节

HandlerMapping 接口中唯一的待实现方法 getHandler 的返回值类型是 HandlerExecutionChain,那 HandlerMapping 是如何构造 HandlerExecutionChain 的呢?具体请看《SpringMVC- 第二篇:控制器方法(handler)映射.md》中的 拦截器的初始化流程 小节。

初始化

初始化

DispatcherServlet#initHandlerMappingsDispatcherServlet#getDefaultStrategies

DispatcherServlet#initHandlerMappings 请看《SpringMVC-DispatcherServlet 源码分析.md》的 initStrategies 小节

在《SpringMVC- 第五篇:视图》就有过关于 DispatcherServlet#getDefaultStrategies 的研究。

一般用不到 DispatcherServlet#getDefaultStrategies,通过注解配置 SpringMVC 的时候(实际上在 WebMvcConfigurationSupport 中),会自动注入在应用上下文中注入默认几个 HandlerMapping 的相应类型的 Bean,

具体生效

doDispatch 执行流程

  1. 经过 HandlerMapping 进行处理器映射,获取 handler,实际上获取的是包含拦截器的处理器执行链 HandlerExecutionChain

  2. 通过 HandlerExecutionChain 中最终的 handler,获取适配器 HandlerAdapter

  3. 执行 HandlerAdapter#handle 方法执行处理器,获得 ModelAndView

  4. 处理 ModelAndView,如果有错误,就已经异常处理,渲染异常解析 HandlerExceptionResolver 返回的 ModelAndView,如果没有错误,正常解析 ModelAndView

DispatcherServletdoDispatch 方法中,通过 getHandler(processedRequest); 获取 HandlerExecutionChain mappedHandler

DispatcherServletgetHandler 方法则是通过遍历字段 handlerMappings,调用 HandlerMappinggetHandler 方法,来获取控制器方法的执行链,找到了就返回,所以 handlerMappings 中的 HandlerMapping 的顺序很重要。

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            // 遍历 handlerMappings 字段,调用 HandlerMapping 的 getHandler 方法,获取控制器方法执行链,找到了就返回
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                // 获取控制器方法执行链,找到了就返回
                // 所以 handlerMappings 的顺序很重要
                return handler;
            }
        }
    }
    return null;
}

实际调试过程中,有三种

可以看到 SimpleUrlHandlerMapping 排在 RequestMappingHandlerMapping 的后面,因此,如果 url 同时匹配控制器方法和 ResourceHttpRequestHandler,那么会优先匹配控制器方法。

ResourceHttpRequestHandler 通常由 SimpleUrlHandlerMapping 进行映射。

从请求路径到处理器的映射具体过程

《SpringMVC-第十篇:基于注解配置SpringMVC》到底用哪一套逻辑呢? 小节,分析了在不同的 HandlerMapping 的实现中,进行从请求路径到控制器的映射的过程。非常值得一看,我们在具体的 HandlerMapping 的实现类的源码解析中也会提到这一部分的内容。具体的细节,请看 HandlerMapping 的实现类的源码解析

RequestMappingHandlerMapping

用于映射 @RequestMapping 表示的控制器方法这种类型的 handler。

关于 RequestMappingHandlerMapping 完整的初始化流程,请看《SpringMVC-RequestMappingHandlerMapping 源码解析.md》。

BeanNameUrlHandlerMapping

HandlerMapping 接口的实现,该接口将 url 映射到名称以斜杠 (/) 开头的 bean 中,类似于 Struts 框架将 url 映射到 action 类的名称的方式。

连同 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping。这两个是 org.springframework.web.servlet.DispatcherServlet 使用的默认的 HandlerMapping 实现。此外,SimpleUrlHandlerMapping 允许声明式地自定义处理程序映射。(SimpleUrlHandlerMappingDispatcherServlet 是默认使用的 HandlerMapping 实现)

映射是从 URL 到 bean 的名称。因此,一个传入的路径为 /foo 的 URL 将映射到一个名称为 /foo 的 handler bean,或者在多个路径映射到单个 handler 的情况下映射到 /foo/foo2

支持直接匹配 (给定 /test ->注册的 handler bean 的名称为 /test) 和 * 通配符匹配 (给定 /test ->注册的 handler bean 的名称 /t*)。注意,默认是在当前 servlet 映射路径中映射 (如果适用的话); 详见 AbstractHandlerMapping#alwaysUseFullPath 属性。有关模式选项的详细信息,请参见 org.springframework.util.AntPathMatcher

有关 AntPathMatcher 的具体解析,请参考《UrlPathHelper+PathMatcher 简单解析.md

现在用的很少了,就不研究了

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping 其实很简单,只用指定这个 HandlerMapping 需要处理的所有 URL 的匹配模式和单个 URL 匹配模式对应的处理器即可,用 urlMap 存起来,初始化的时候传入即可。

SimpleUrlHandlerMapping 的应用非常广泛,在 WebMvcConfigurationSupport#defaultServletHandlerMappingWebMvcConfigurationSupport#addViewControllersWebMvcConfigurationSupport#addResourceHandlers 中均有使用

将来我们需要自定义 HandlerMapping 用这个就是最方便的

有关 RouterFunctionMapping 的疑问

有一个问题,明明 WebMvcConfigurationSupport 中的 bean,为什么在 SpringMVC 中没有生效,在 SpringBoot 中却生效了,还没搞清楚。?

自定义 HandlerMapping

当我们需要一些自定义的请求映射的时候,我们可以在容器中添加我们自定义的 HandlerMapping,注意设置 order 顺序

自定义 handlermapping 的版本的一个场景是,/api/v1 ,/api/v2 去不同的包下面找 handler,这个在《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》中的 pathPrefixes 字段 小节中,已经实现了。